/* $RCSFile: CssHelper.cs $
 * Apache license v2 2011 by Christian Surieux
 * $Source: ?/ADNCRM.Core/Helpers/CssHelper.cs $
 * $log$
 * Revision 6 2010/08/06 19:56:28  christian.surieux
 *   Added Log to All cs files
 */

using System;
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Text;
using System.Web;
using System.Globalization;
using System.Xml;
using System.Xml.XPath;
using AddonNice.Configuration;
using AddonNice.Security;
using AddonNice.Diagnostics;

namespace AddonNice.Helpers
{
	/// <summary>
	/// CssHelper object 
	/// </summary>
	public class CssHelper
	{
		PortalSettings portalSettings;

		private const string SELECTOR_PREFIX = "";
		private const bool ALLOW_IMPORTS = true;
		private const bool PARSE_IMPORTS = true;
		private const bool INCLUDE_COMMENTS = true;

		public CssHelper()
		{
			if (HttpContext.Current != null)
			{
				portalSettings = (PortalSettings) HttpContext.Current.Items[StStr.strPortalSettings];
			}
		}

		public string ParseCss(string cssFileName)
		{
			return ParseCss(cssFileName, SELECTOR_PREFIX);
		}

		public string ParseCss(string cssFileName, string selectorPrefix)
		{
			return ParseCss(cssFileName, selectorPrefix, ALLOW_IMPORTS);
		}

		public string ParseCss(string cssFileName, string selectorPrefix, bool allowImports)
		{
			return ParseCss(cssFileName, selectorPrefix, allowImports, PARSE_IMPORTS);
		}

		public string ParseCss(string cssFileName, string selectorPrefix, bool allowImports, bool parseImports)
		{
			return(ParseCss(cssFileName, selectorPrefix, allowImports, parseImports, INCLUDE_COMMENTS));
		}

		public string ParseCss(string cssFileName, string selectorPrefix, bool allowImports, bool parseImports, bool includeComments)
		{
			return(ParseCss(cssFileName, selectorPrefix, allowImports, parseImports, includeComments, null));
		}

		public string ParseCss(string cssFileName, string selectorPrefix, bool allowImports, bool parseImports, bool includeComments, StringBuilder sb)
		{
			StreamReader sr = new StreamReader(cssFileName);
			StringTokenizer st = new StringTokenizer(sr);
			if ( sb == null )
				sb = new StringBuilder();
			Token token;

			try
			{
				do 
				{
					token = st.Next();

					switch ( token.Kind )
					{
						case TokenKind.Comment :
							if ( includeComments )
							{
								sb.Append(token.Value);
								//sb.Append("\n");
							}
							break;

						case TokenKind.Selector :
							if ( selectorPrefix == string.Empty )
								sb.Append(token.Value);
							else
							{
								sb.Append(selectorPrefix);
								sb.Append(" ");
								sb.Append(token.Value);
							}
							break;

						case TokenKind.AtRule :
						case TokenKind.Block :
							sb.Append(token.Value);
							break;

						case TokenKind.ImportRule :
							if ( allowImports && parseImports)
							{
								// temp
								//sb.Append(token.Value);
								string _filename = token.Value.Replace("@import","");
								_filename = _filename.Replace("url","");
								_filename = _filename.Replace("(","");
								_filename = _filename.Replace(")","");
								_filename = _filename.Replace("'","");
								_filename = _filename.Replace("\"","");
								_filename = _filename.Replace(";","").Trim();
								_filename = string.Concat(cssFileName.Substring(0,cssFileName.LastIndexOf(@"\")).Trim(),"\\", _filename);
								CssHelper _loop = new CssHelper();
								_loop.ParseCss(_filename, selectorPrefix, allowImports, parseImports, includeComments, sb);
							}
							else if ( allowImports && !parseImports )
							{
								sb.Append(token.Value);
							}
							break;

						default: 
							sb.Append(token.Value);
							break;
					}

				
				} while ( token.Kind != TokenKind.EOF );
			}
			catch(Exception ex)
			{
                if ( GlobalTraceSwitch.Sw.TraceError )
                    Trace.WriteLine( string.Format(CultureInfo.InvariantCulture,"CssHelper ParseCss cssFileName : {0}, ex : {1}",cssFileName,ex),GlobalTraceSwitch.Sw.Info);
				throw;
			}
			finally
			{
				sr.Close();	
			}

			return sb.ToString();
		}

	}

	/********************************************************
	*	Author: Andrew Deren
	*	Date: July, 2004
	*	http://www.adersoftware.com
	* 
	*	StringTokenizer class. You can use this class in any way you want
	* as long as this header remains in this file.
	* 
	**********************************************************/

	/// <summary>
	/// StringTokenizer tokenized string (or stream) into tokens.
	/// </summary>
	public class StringTokenizer
	{
		const char EOF = (char)0;

		int line;
		int column;
		int pos;	// position within data

		string data;

		bool ignoreWhiteSpace;
		char[] symbolChars;

		int saveLine;
		int saveCol;
		int savePos;

		public StringTokenizer(StreamReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");

			data = reader.ReadToEnd();

			Reset();
		}

		public StringTokenizer(string data)
		{
			if (data == null)
				throw new ArgumentNullException("data");

			this.data = data;

			Reset();
		}

		/// <summary>
		/// gets or sets which characters are part of TokenKind.Symbol
		/// </summary>
		public char[] SymbolChars
		{
			get { return this.symbolChars; }
			set { this.symbolChars = value; }
		}

		/// <summary>
		/// if set to true, white space characters will be ignored,
		/// but EOL and whitespace inside of string will still be tokenized
		/// </summary>
		public bool IgnoreWhiteSpace
		{
			get { return this.ignoreWhiteSpace; }
			set { this.ignoreWhiteSpace = value; }
		}

		private void Reset()
		{
			this.ignoreWhiteSpace = false;
			this.symbolChars = new char[]{'=', '+', '-', '/', ',', '.', '*', '~', '!', '@', '#', '$', '%', '^', '&', '(', ')', '{', '}', '[', ']', ':', ';', '<', '>', '?', '|', '\\'};

			line = 1;
			column = 1;
			pos = 0;
		}

		protected char LA(int count)
		{
			if (pos + count >= data.Length)
				return EOF;
			else
				return data[pos+count];
		}

		protected char Consume()
		{
			char ret = data[pos];
			pos++;
			column++;

			return ret;
		}

		protected Token CreateToken(TokenKind kind, string value)
		{
			return new Token(kind, value, line, column);
		}

		protected Token CreateToken(TokenKind kind)
		{
			string tokenData = data.Substring(savePos, pos-savePos);
			return new Token(kind, tokenData, saveLine, saveCol);
		}

		public Token Next()
		{
			ReadToken:
			char ch = LA(0);
			switch (ch)
			{
				case EOF:
					return CreateToken(TokenKind.EOF, string.Empty);

				case ' ':
				case '\t':
				{
					if (this.ignoreWhiteSpace)
					{
						Consume();
						goto ReadToken;
					}
					else
						return ReadWhitespace();
				}

				case '\r':
				{
					StartRead();
					Consume();
					if (LA(0) == '\n')
						Consume();	// on DOS/Windows we have \r\n for new line

					line++;
					column=1;

					return CreateToken(TokenKind.EOL);
				}
				case '\n':
				{
					StartRead();
					Consume();
					line++;
					column=1;
					
					return CreateToken(TokenKind.EOL);
				}

				case '@':
				{
					if (LA(1) == 'i' || LA(1) == 'I') // import rule
						return ReadImportRule();
					else
						return ReadAtRule();
				}

				case '/':
				{
					if (LA(1) == '*') // comment
						return ReadComment();
					else
					{
						Consume();
						return CreateToken(TokenKind.Symbol);
					}
				}

				case '{': // block
				{
					return ReadBlock();
				}

				default: // selector
				{
					return ReadSelector();
					//					if (Char.IsLetter(ch) || ch == '_')
					//						return ReadWord();
					//					else if (IsSymbol(ch))
					//					{
					//						StartRead();
					//						Consume();
					//						return CreateToken(TokenKind.Symbol);
					//					}
					//					else
					//					{
					//						StartRead();
					//						Consume();
					//						return CreateToken(TokenKind.Unknown);						
					//					}
				}

			}
		}

		/// <summary>
		/// save read point positions so that CreateToken can use those
		/// </summary>
		private void StartRead()
		{
			saveLine = line;
			saveCol = column;
			savePos = pos;
		}

		/// <summary>
		/// reads all whitespace characters (does not include newline)
		/// </summary>
		/// <returns></returns>
		protected Token ReadWhitespace()
		{
			StartRead();

			Consume(); // consume the looked-ahead whitespace char

			while (true)
			{
				char ch = LA(0);
				if (ch == '\t' || ch == ' ')
					Consume();
				else
					break;
			}

			return CreateToken(TokenKind.WhiteSpace);
			
		}


		protected Token ReadBlock()
		{
			StartRead();

			Consume(); // read '{'

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
					break;
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == '}')
				{
					Consume();
					break;
				}
				else
					Consume();
			}

			return CreateToken(TokenKind.Block);
		}

		protected Token ReadComment()
		{
			StartRead();

			Consume(); // read '/'
			Consume(); // read '*'

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
					break;
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == '*' && LA(1) == '/')
				{
					Consume(); // read '*'
					Consume(); // read '/'
					break;
				}
				else
					Consume();
			}

			return CreateToken(TokenKind.Comment);
		}


		protected Token ReadImportRule()
		{
			StartRead();

			Consume(); // read '@'

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
					break;
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == ';')
				{
					Consume(); // read ';'
					break;
				}
				else
					Consume();
			}

			return CreateToken(TokenKind.ImportRule);
		}

		protected Token ReadAtRule()
		{
			StartRead();

			Consume(); // read '@'

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
					break;
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == ';')
				{
					Consume(); // read ';'
					break;
				}
				else if (ch == '{')
					break;
				else
					Consume();
			}

			return CreateToken(TokenKind.AtRule);
		}

		protected Token ReadSelector()
		{
			StartRead();

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
				{
					return CreateToken(TokenKind.Error); // shouldn't encounter this
				}
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == ';')
				{
					Consume(); // read ';' - shouldn't encounter this
					return CreateToken(TokenKind.Error);
				}
				else if (ch == '{')
					break;
				else
					Consume();
			}

			return CreateToken(TokenKind.Selector);
		}

		/// <summary>
		/// reads number. Number is: DIGIT+ ("." DIGIT*)?
		/// </summary>
		/// <returns></returns>
		protected Token ReadNumber()
		{
			StartRead();

			bool hadDot = false;

			Consume(); // read first digit

			while (true)
			{
				char ch = LA(0);
				if (Char.IsDigit(ch))
					Consume();
				else if (ch == '.' && !hadDot)
				{
					hadDot = true;
					Consume();
				}
				else
					break;
			}

			return CreateToken(TokenKind.Number);
		}

		/// <summary>
		/// reads word. Word contains any alpha character or _
		/// </summary>
		protected Token ReadWord()
		{
			StartRead();

			Consume(); // consume first character of the word

			while (true)
			{
				char ch = LA(0);
				if (Char.IsLetter(ch) || ch == '_')
					Consume();
				else
					break;
			}

			return CreateToken(TokenKind.Word);
		}

		/// <summary>
		/// reads all characters until next " is found.
		/// If "" (2 quotes) are found, then they are consumed as
		/// part of the string
		/// </summary>
		/// <returns></returns>
		protected Token ReadString()
		{
			StartRead();

			Consume(); // read "

			while (true)
			{
				char ch = LA(0);
				if (ch == EOF)
					break;
				else if (ch == '\r')	// handle CR in strings
				{
					Consume();
					if (LA(0) == '\n')	// for DOS & windows
						Consume();

					line++;
					column = 1;
				}
				else if (ch == '\n')	// new line in quoted string
				{
					Consume();

					line++;
					column = 1;
				}
				else if (ch == '"')
				{
					Consume();
					if (LA(0) != '"')
						break;	// done reading, and this quotes does not have escape character
					else
						Consume(); // consume second ", because first was just an escape
				}
				else
					Consume();
			}

			return CreateToken(TokenKind.QuotedString);
		}

		/// <summary>
		/// checks whether c is a symbol character.
		/// </summary>
		protected bool IsSymbol(char c)
		{
			for (int i=0; i<symbolChars.Length; i++)
				if (symbolChars[i] == c)
					return true;

			return false;
		}
	}

	/********************************************************
	*	Author: Andrew Deren
	*	Date: July, 2004
	*	http://www.adersoftware.com
	* 
	*	StringTokenizer class. You can use this class in any way you want
	* as long as this header remains in this file.
	* 
	**********************************************************/

	public enum TokenKind
	{
		Unknown,
		Word,
		Number,
		QuotedString,
		WhiteSpace,
		Symbol,
		EOL,
		EOF,
		Comment,
		Selector,
		Block,
		AtRule,
		ImportRule,
		Error
	}

	public class Token
	{
		int line;
		int column;
		string value;
		TokenKind kind;

		public Token(TokenKind kind, string value, int line, int column)
		{
			this.kind = kind;
			this.value = value;
			this.line = line;
			this.column = column;
		}

		public int Column
		{
			get { return this.column; }
		}

		public TokenKind Kind
		{
			get { return this.kind; }
		}

		public int Line
		{
			get { return this.line; }
		}

		public string Value
		{
			get { return this.value; }
		}
	}

}

